"""--------------------------------------------------------------------------------------------------
Filename:   controls.py
Author:     Ben Han (uha10@uclive.ac.nz)
Date:       17 May 2010
Description:
    Main plotting module for biofeedback application
--------------------------------------------------------------------------------------------------"""

import random
from matplotlib.patches import Rectangle

"""--------------------------------------------------------------------------------------------------
  CLASS: TargetFactory
    This class creates and returns the targets for the skill training.
--------------------------------------------------------------------------------------------------"""
class TargetFactory:
    """ Holds information about the target polygon
        position[x,y] gives the coordinates of the target (bottom left, top right corners respectively)
        size[x,y] holds the dimensions of the target
    """

    def __init__(self, axes, cfg, state):
        """ Constructor takes 2 arguments.
            axes = reference to the axes object onto which the target is placed
            cfg = configuration object
        """
        self.axes = axes
        self.cfg = cfg
        self.sess_status = state
        self.x_lim = cfg.TRIAL_LEN
        
        self.position = [0, 0]      # target coordinates (bottom left corner)
        self.size = [0, 0]          # target dimensions
        self.range_min = [0, 0]     # coordinates of the bottom left corner of the range [x,y]
        self.range_max = [0, 0]     # coordinates of the top right corner of the range [x,y]
        self.size_min = [0, 0]      # minimum values for the target size [x,y]
        self.size_max = [0, 0]      # maximum values fot the target size [x,y]
        
    def set_range_min(self, min):
        self.range_min = min
        
    def set_range_max(self, max):
        self.range_max = max
        
    def set_size_limits(self):
        self.size_min[0] = self.sess_status.min_size[0]
        self.size_max[0] = self.sess_status.max_size[0]
        self.size_min[1] = self.sess_status.min_size[1]
        self.size_max[1] = self.sess_status.max_size[1]

    def get_limits(self):
        limits = [self.range_min[0], self.range_max[0], self.range_min[1], self.range_max[1], \
                  self.size_min[0], self.size_max[0], self.size_min[1], self.size_max[1], \
                  self.axes, self.cfg, self.x_lim]
        return limits

    def recalc(self, change, init=False):
        """ Recalculate the target position and size
        """
        adj = 1 - (float(self.cfg.increment) / 100)
        if self.sess_status.trial_index == 1:
            init = True
        # adjust target size based on history of hits/misses
        if init:
            # Set initial target size
            self.size[0] = self.cfg.INIT_SIZE_X
            self.size[1] = self.sess_status.baseline * self.cfg.INIT_SIZE_Y / 100
        elif change > 0:
            self.size[1] = self.size[1] / adj    # increase size
            self.size[0] = self.size[0] / adj
        elif change < 0:
            self.size[1] = self.size[1] * adj    # decrease size
            self.size[0] = self.size[0] * adj

        """ Set random position on the screen within given range, and ensure that the target
            is completely inside the range limits.
        """
        # Firstly, test to see if the target is able to fit completely inside the range limits,
        # otherwise set the size to the limit.
        if self.size[1] > (self.range_max[1] - self.range_min[1]):     # amplitude size
            self.size[1] = self.range_max[1] - self.range_min[1]
        if self.cfg.targetAspectRatio:
            # Make target look square on screen
            self.box = Rectangle(self.position, self.size[0], self.size[1], color=self.cfg.targetColour)
            self.square_target(self.box, self.axes.transData)
            self.size[0] = self.box.get_width()
            
        if self.size[0] > (self.range_max[0] - self.range_min[0]):     # temporal size
            self.size[0] = self.range_max[0] - self.range_min[0]

        # Set (r1, r2) to (lower left, upper right) limits for random placement
        r1 = [ self.range_min[0], self.range_min[1] ]
        r2 = [ self.range_max[0] - self.size[0], self.range_max[1] - self.size[1] ]
        # Set the bottom left corner of the target to a random point inside the range
        self.position[0] = (r2[0] - r1[0]) * random.random() + r1[0]
        self.position[1] = (r2[1] - r1[1]) * random.random() + r1[1]
        
        self.x1 = self.position[0]
        self.y1 = self.position[1]
        self.x2 = self.position[0] + self.size[0]
        self.y2 = self.position[1] + self.size[1]
        print "=== Target parameters ================================"
        print "    pos     (x,y): (%.2f, %.2f)" % (self.position[0], self.position[1])
        print "    size    (x,y): (%.2f, %.2f)" % (self.size[0], self.size[1])
        print "======================================================"

        # Create the target box, place it on the subplot and return the reference
        self.box = Rectangle(self.position, self.size[0], self.size[1], color=self.cfg.targetColour)
            
        # Return the box after adding it to the plot axes
        return self.axes.add_patch(self.box)

    def redraw(self, color):
        """ Returns the target with the same coordinates but with given color
        """
        if self.sess_status.target:
            self.box.set_color(color)
            return self.box
        else:
            self.box = Rectangle((self.position[0], self.position[1]), self.size[0], self.size[1], color=color)
            return self.axes.add_patch(self.box)

    def square_target(self, box, trans):
        # Accepts a matplotlib.patches.Rectangle and a transform object.
        # Sets the Rectangle attributes so that width == original height 
        # in the transformed domain described by the transform provided. 
        # The function uses the centre of the box as a stationary reference point.
                
        # Get the current values for coordinates
        x = box.get_x(); y = box.get_y(); w = box.get_width(); h = box.get_height()
        x2 = x + w; y2 = y + h      # upper right point
        cx = x + w/2; cy = y + h/2  # center point
        
        # Make an inverse transform object for getting back to dataspace coords
        inv = trans.inverted()
        
        # Transform the coordinates to pixel space and calculate new width
        lowerleft = trans.transform((x, y))
        upperright = trans.transform((x2, y2))
        center = trans.transform((cx, cy))
        pixel_width = upperright[1] - lowerleft[1]  # <-- pixel_height
        
        # Set the coordinates to their new values
        new_x = center[0] - pixel_width/2
        new_x2 = new_x + pixel_width
        lowerleft = (new_x, lowerleft[1])
        upperright = (new_x2, upperright[1])
        
        # Perform an inverse transformation to return to dataspace coords
        DLL = inv.transform(lowerleft)      # Dataspace Lower Left
        DUR = inv.transform(upperright)     # Dataspace Upper Right
        
        # Set the Rectangle object's attributes to the new values
        box.set_x(DLL[0])       # y values are unchanged, so only set x
        box.set_width(DUR[0] - DLL[0])      # width is most likely changed, so set

# End of targetfactory.py